package com.hcc.repository.core.handler.annotation;

import com.hcc.repository.core.annotation.QueryProvider;
import com.hcc.repository.core.conditions.ICondition;
import com.hcc.repository.core.conditions.nativesql.NativeSqlCondition;
import com.hcc.repository.core.utils.Assert;
import com.hcc.repository.core.utils.ReflectUtils;
import com.hcc.repository.core.utils.StrUtils;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Map;

/**
 * 查询注解处理器
 *
 * @author hushengjun
 * @date 2023/4/28
 */
@Slf4j
public class QueryProviderAnnotationMethodHandler extends QueryAnnotationMethodHandler {

    private final QueryProvider queryProviderAnnotation;
    private Class<?> providerClassType;
    private Method providerMethod;

    public QueryProviderAnnotationMethodHandler(QueryProvider queryProviderAnnotation) {
        super(queryProviderAnnotation.resultMapper());
        this.queryProviderAnnotation = queryProviderAnnotation;
    }

    @Override
    protected void prepare() {
        // Provider类名
        providerClassType = this.lookupProviderClass();
        Assert.isFalse(void.class.equals(providerClassType), "需要指定value或type");
        Assert.isFalse(providerClassType.isInterface(), "provider class不能为interface");

        // 查找对应的Provider方法
        providerMethod = this.lookupProviderMethod();

        // 判断返回值必须为String
        Class<?> returnType = providerMethod.getReturnType();
        Assert.isTrue(String.class.equals(returnType), "{0}方法返回值必须为String", providerMethod);
    }

    /**
     * 查找ProviderClass
     * @return
     */
    private Class<?> lookupProviderClass() {
        Class<?> providerClass = queryProviderAnnotation.value();
        if (providerClass == null || void.class.equals(providerClass)) {
            providerClass = queryProviderAnnotation.type();
        }
        if (providerClass == null || void.class.equals(providerClass)) {
            // 默认的Provider
            String defaultProviderName = method.getDeclaringClass().getPackage().getName()
                    + ".provider."
                    + method.getDeclaringClass().getSimpleName()
                    + "Provider";
            providerClass = ReflectUtils.forName(defaultProviderName);
        }

        return providerClass;
    }

    /**
     * 查找ProviderMethod
     * @return
     */
    private Method lookupProviderMethod() {
        // Provider方法名，不指定则使用与mapper同名方法
        String providerMethodName = queryProviderAnnotation.method();
        if (StrUtils.isEmpty(providerMethodName)) {
            providerMethodName = method.getName();
        }

        Method providerMethod;
        try {
            providerMethod = ReflectUtils.getDeclaredMethod(providerClassType, providerMethodName, Map.class);
        } catch (Exception e) {
            providerMethod = ReflectUtils.getDeclaredMethod(providerClassType, providerMethodName, method.getParameterTypes());
        }

        return providerMethod;
    }

    @Override
    protected ICondition<?> prepareCondition() {
        boolean isStaticMethod = Modifier.isStatic(providerMethod.getModifiers());

        Object providerObject = null;
        if (!isStaticMethod) {
            // 非static方法需实例化Provider类调用
            providerObject = ReflectUtils.newInstance(providerClassType);
        }

        // 全局的参数map
        Map<String, Object> globalParamMap = super.collectParam();

        // 执行方法并获取到sql
        String sql;
        if (providerMethod.getParameterTypes().length == 1 && Arrays.stream(providerMethod.getParameterTypes()).allMatch(p -> p.equals(Map.class))) {
            sql = ReflectUtils.invokeMethod(providerObject, providerMethod, String.class, globalParamMap);
        } else {
            sql = ReflectUtils.invokeMethod(providerObject, providerMethod, String.class, args);
        }
        if (log.isDebugEnabled()) {
            log.debug("方法 {}执行结果为：{}", providerMethod, sql);
        }
        Assert.isTrue(StrUtils.isNotEmpty(sql), "方法 {0}生成的sql为空", providerMethod);

        // 构建Condition
        NativeSqlCondition<?> condition = new NativeSqlCondition<>();
        condition.sql(sql);
        condition.putParamMap(globalParamMap);

        return condition;
    }

}
